/*
 * Opus音频录制器
 * 该程序用于录制音频并通过Opus编码压缩后保存到文件
 */

#include <alsa/asoundlib.h>  // ALSA音频库，用于Linux音频设备操作
#include <opus/opus.h>       // Opus编解码器库
#include <signal.h>          // 信号处理库

#define OFILE "record.opus"  // 录制音频保存的文件名

/*
 * 录音器结构体
 * 包含录音所需的所有信息和资源
 */
struct recorder {
    snd_pcm_t* device;       // ALSA PCM设备句柄
    int channels;            // 音频声道数
    int sample_rate;         // 采样率
    int duration;            // 周期持续时间（毫秒）
    int frames;              // 每个周期的帧数
    OpusEncoder* encoder;    // Opus编码器实例
    opus_int16* buffer;      // 音频数据缓冲区
};

/*
 * 创建录音器实例
 * @param name: 设备名称
 * @param sample_rate: 采样率
 * @param channels: 声道数
 * @param duration: 周期持续时间（毫秒）
 * @return: 成功返回recorder指针，失败返回NULL
 */
struct recorder* recorder_create(const char* name, int sample_rate, int channels, int duration)
{
    // 分配录音器结构体内存
    struct recorder* recorder = malloc(sizeof(struct recorder));
    if (!recorder)
    {
        return NULL;
    }

    // 初始化基本参数
    recorder->channels = channels;
    recorder->sample_rate = sample_rate;
    recorder->duration = duration;

    // 打开PCM录音设备
    int error = snd_pcm_open(&recorder->device, name, SND_PCM_STREAM_CAPTURE, 0);
    if (error < 0)
    {
        fprintf(stderr, "打开设备 %s 失败: %s\n", name, snd_strerror(error));
        free(recorder);
        return NULL;
    }

    // 设置PCM参数
    error = snd_pcm_set_params(recorder->device,
                            SND_PCM_FORMAT_S16_LE,          // 16位小端格式
                            SND_PCM_ACCESS_RW_INTERLEAVED,  // 交错访问模式
                            recorder->channels,             // 声道数
                            recorder->sample_rate,          // 采样率
                            1,                              // 是否允许软件重采样
                            recorder->duration * 1000);     // 延迟时间（纳秒）
    if (error < 0) {
        fprintf(stderr, "设置PCM参数失败: %s\n", snd_strerror(error));
        snd_pcm_close(recorder->device);
        free(recorder);
        return NULL;
    }

    // 创建Opus编码器
    recorder->encoder = opus_encoder_create(recorder->sample_rate, recorder->channels, OPUS_APPLICATION_AUDIO, &error);
    if (error != OPUS_OK) {
        fprintf(stderr, "创建Opus编码器失败: %s\n", opus_strerror(error));
        snd_pcm_close(recorder->device);
        free(recorder);
        return NULL;
    }

    // 计算每周期帧数并分配缓冲区
    recorder->frames = recorder->sample_rate * recorder->duration / 1000;
    recorder->buffer = malloc(snd_pcm_frames_to_bytes(recorder->device, recorder->frames));
    if (!recorder->buffer) {
        opus_encoder_destroy(recorder->encoder);
        snd_pcm_close(recorder->device);
        free(recorder);
        return NULL;
    }

    return recorder;
}

/*
 * 销毁录音器实例并释放资源
 * @param recorder: 要销毁的录音器实例
 */
void recorder_destroy(struct recorder *recorder)
{
    if (recorder) {
        // 释放缓冲区内存
        if (recorder->buffer) {
            free(recorder->buffer);
            recorder->buffer = NULL;
        }
        // 关闭PCM设备
        if (recorder->device) {
            snd_pcm_close(recorder->device);
            recorder->device = NULL;
        }
        // 销毁Opus编码器
        if (recorder->encoder) {
            opus_encoder_destroy(recorder->encoder);
            recorder->encoder = NULL;
        }
        // 释放录音器结构体
        free(recorder);
    }
}

/*
 * 启动录音器
 * @param recorder: 录音器实例
 * @return: 成功返回0，失败返回负值
 */
int recorder_start(struct recorder *recorder)
{
    if (!recorder)
    {
        return -1;
    }

    return snd_pcm_start(recorder->device);
}

/*
 * 停止录音器
 * @param recorder: 录音器实例
 * @return: 成功返回0，失败返回负值
 */
int recorder_stop(struct recorder *recorder)
{
    if (!recorder)
    {
        return -1;
    }

    return snd_pcm_drain(recorder->device);
}

/*
 * 录制音频数据
 * @param recorder: 录音器实例
 * @param data: 用于存储编码后数据的缓冲区
 * @param size: 缓冲区大小
 * @return: 成功返回编码后的字节数，失败返回负值
 */
int32_t record_audio(struct recorder* recorder, char* data, size_t size)
{
    if (!recorder)
    {
        return -1;
    }

    // 从PCM设备读取音频数据
    snd_pcm_sframes_t frames = snd_pcm_readi(recorder->device, recorder->buffer, recorder->frames);
    if (frames < 0) {
        fprintf(stderr, "PCM读取失败: %s\n", snd_strerror(frames));
        return -1;
    }
    
    // 使用Opus编码器编码音频数据
    opus_int32 enc_bytes = opus_encode(recorder->encoder, recorder->buffer, frames, data, size);
    if (enc_bytes < 0)
    {
        fprintf(stderr, "Opus编码失败: %s\n", opus_strerror(enc_bytes));
        return -1;
    }

    return enc_bytes;
}

// 全局变量用于信号处理，控制录音是否继续
static volatile sig_atomic_t running = 1;

/*
 * 信号处理函数
 * 当接收到中断信号时设置running为0，停止录音
 * @param signum: 信号编号
 */
void signal_handler(int signum)
{
    running = 0;
}

// 文件写入缓冲区
char file_buffer[4096];

int main()
{
    // 设置信号处理，捕获Ctrl+C信号以正常停止录音
    signal(SIGINT, signal_handler);

    // 创建录音器实例
    struct recorder* recorder = recorder_create("default", 16000, 1, 20);
    if (!recorder)
    {
        return EXIT_FAILURE;
    }

    // 打开音频文件用于写入
    FILE * fp = fopen(OFILE, "wb");
    if (!fp)
    {
        perror(OFILE);
        return EXIT_FAILURE;
    }

    // 开始录音
    recorder_start(recorder);

    // 循环录制音频数据直到接收到中断信号
    while(running)
    {
        // 录制并编码音频数据
        int32_t bytes = record_audio(recorder, file_buffer, sizeof(file_buffer));
        if (bytes < 0)
        {
            break;
        }

        printf("写入 %d 字节\n", bytes);
        
        // 将音频数据写入文件
        // 先写入数据块大小（帧头）
        if (fwrite(&bytes, 1, sizeof(bytes), fp) != sizeof(bytes))
        {
            fprintf(stderr, "写入不完整\n");
            break;
        }

        // 再写入实际的音频数据（帧数据）
        if (fwrite(file_buffer, 1, bytes, fp) != bytes)
        {
            fprintf(stderr, "写入不完整\n");
            break;
        }
    }

    // 停止录音并清理资源
    recorder_stop(recorder);

    fclose(fp);

    recorder_destroy(recorder);

    return EXIT_SUCCESS;
}